package com.example.demo.filter;

import com.alibaba.fastjson.JSON;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class GatewayMetricsGatewayFilter implements GatewayFilter, Ordered {

  public GatewayMetricsGatewayFilter() {
    log.info("RiskEngineGatewayMetricsGatewayFilter 启用");
  }

  private static final String START_TIME = "ZeusRiskEngineMetricsStartTime";

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
    return chain.filter(exchange).doOnSuccess(aVoid -> endTimerRespectingCommit(exchange, null))
        .doOnError(throwable -> endTimerRespectingCommit(exchange, throwable));
  }

  private void endTimerRespectingCommit(ServerWebExchange exchange, Throwable myThrowable) {
    ServerHttpResponse response = exchange.getResponse();
    if (response.isCommitted()) {
      endTimerInner(exchange, myThrowable);
    }
    else {
      response.beforeCommit(() -> {
        endTimerInner(exchange, myThrowable);
        return Mono.empty();
      });
    }
  }

  private void endTimerInner(ServerWebExchange exchange, Throwable throwable) {
    Long startTime = exchange.getAttribute(START_TIME);
    if (startTime != null) {
      long executeTime = (System.currentTimeMillis() - startTime);
      String requestBody = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);
      String command = JSON.parseObject(requestBody).getString("command");
      log.error("Time Monitor: command: {} SPEND {} MS", command, executeTime, throwable);
      CircuitBreaker circuitBreaker = FallbackGatewayFilter.circuitBreakers.get(command);
      if (circuitBreaker != null) {
        circuitBreaker.onSuccess(executeTime, TimeUnit.MILLISECONDS);
      }
    }
  }

  @Override
  public int getOrder() {
    return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
  }
}
